package com.gitee.security.filter;

import com.alibaba.fastjson.JSON;
import com.gitee.bo.UserDetailsBo;
import com.gitee.config.JWTConfig;
import com.gitee.contant.Constants;
import com.gitee.exception.ServiceException;
import com.gitee.result.Result;
import com.gitee.service.impl.UserDetailsServiceImpl;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @description: 自定义JWT登录过滤器
     * 验证用户名密码正确后，生成一个token，并将token返回给客户端
     * 该类继承自UsernamePasswordAuthenticationFilter，重写了其中的2个方法
     * attemptAuthentication ：接收并解析用户凭证。
     * successfulAuthentication ：用户成功登录后，这个方法会被调用，在这个方法里生成token。
 * @author: chennl
 * @date: 2021/12/28
 * @version: 1.0
 */
@Slf4j
public class JWTLoginFilter extends UsernamePasswordAuthenticationFilter {

    private final AuthenticationManager authenticationManager;
    private final JWTConfig jwtConfig;
    private final UserDetailsServiceImpl userDetailsService;

    public JWTLoginFilter(AuthenticationManager authenticationManager,
                          JWTConfig jwtConfig,
                          UserDetailsServiceImpl userDetailsService) {
        this.authenticationManager = authenticationManager;
        this.jwtConfig = jwtConfig;
        this.userDetailsService = userDetailsService;
    }

    /**
     * 尝试认证(接收并解析用户凭证)
     * @param request
     * @param response
     * @return
     * @throws AuthenticationException
     */
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        try {
            String username = request.getParameter("username");
            String password = request.getParameter("password");
            UserDetailsBo userDetails = userDetailsService.loadUserByUsername(username);
            return authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(
                            userDetails, password, userDetails.getAuthorities()));
        } catch (Exception e) {
            throw new ServiceException("认证失败");
        }
    }

    /**
     * 认证成功(用户成功登录后，这个方法会被调用，在这个方法里生成token)
     * @param request
     * @param response
     * @param chain
     * @param auth
     * @throws IOException
     * @throws ServletException
     */
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication auth) throws IOException, ServletException {

        try {
            Map<String, Object> claims = new HashMap<>();
            claims.put(Constants.CLAIM_KEY_USERNAME, auth.getName());
            claims.put(Constants.CLAIM_KEY_CREATED, new Date());
            String token = Jwts.builder()
                    .setClaims(claims)
                    .setIssuedAt(new Date())//签发时间
                    .setExpiration(new Date(System.currentTimeMillis() + jwtConfig.getExpiration()))//过期时间
                    .signWith(SignatureAlgorithm.HS512, jwtConfig.getSecret()) //JWT签名算法
                    .compact();
            // 生成token end

            // 登录成功后，返回token到body里面
            Map<String, Object> resultMap = new HashMap<>();
            resultMap.put(jwtConfig.getTokenHeader(), jwtConfig.getTokenPrefix() + token);
            Result result = Result.ok(resultMap);
            response.getWriter().write(JSON.toJSONString(result));
        } catch (Exception e) {
            throw new ServiceException("登录失败");
        }
    }
}
